# LeetCode 1248、统计「优美子数组」

# 一、题目描述

给你一个整数数组 nums 和一个整数 k。如果某个连续子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。

请返回这个数组中 「优美子数组」 的数目。

示例 1:

输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。

示例 2:

输入:nums = [2,4,6], k = 1
输出:0
解释:数列中不包含任何奇数,所以不存在优美子数组。

示例 3:

输入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
输出:16

提示:

  • 1 <= nums.length <= 50000
  • 1 <= nums[i] <= 10^5
  • 1 <= k <= nums.length

# 二、题目解析

# 三、参考代码

# 1、Java 代码

// 登录 AlgoMooc 官网获取更多算法图解
// https://www.algomooc.com
// 作者:程序员吴师兄
// 微信:wzb_3377
// 代码有看不懂的地方一定要私聊咨询吴师兄呀
// 统计「优美子数组」(LeetCode 1248):https://leetcode.cn/problems/count-number-of-nice-subarrays/
class Solution {
    public int numberOfSubarrays(int[] nums, int k) {
        // [1,1,0,1,1]
        // 统计和为 K 的子数组的数量
        int count = 0;
        
        // 记录遍历到索引为 i 的这个元素时,前缀和的值是多少
        int pre = 0;

        // 利用哈希表,以前缀和为键,出现次数为对应的值,记录 pre[i] 出现的次数 
        HashMap <Integer,Integer> mp = new HashMap <>();
        
        // 一开始,需要设置前缀和为 0 时,出现的次数为 1 次
        // 这一行的作用就是为了应对 nums[0] +nums[1] + ... + nums[i] == k 这种情况
        // 如数组 [1, 2, 3, 6]
        // 这个数组的累加和数组为 [1, 3, 【6】, 12] 
        // 如果 k = 6, 假如 mp 中没有预先存储(0, 1) 
        // 那么来到累加和为 6 的位置时,这时 mp 中存储的就只有两个数据 (1, 1), (3, 1)
        // 想去判断 mp.containsKey(pre - k) , 这时 pre - k = 6 - 6 = 0
        // 但 map 中没有 (0, 1) ,
        // 因为这个时候忽略了从下标 0 累加到下标 i 等于 k 的情况
        // 仅仅是统计了从下标大于 0 到某个位置等于 k 的所有答案
        mp.put(0, 1);

        // 开始从头到尾遍历 nums 数组,在遍历过程中,会执行两个操作
        // 1、存储索引为 i 的这个元素时,前缀和的值是多少,并且把这个值出现的频次存储到 mp 中
        // 2、判断之前有没有存储 pre - k 这种前缀和,如果有,说明 pre - k 到 pre 直接的那些元素值之和就是 k
        for (int i = 0; i < nums.length; i++) {
            
            // [1,3,2,3,5]
            // 奇数看成 1 ,偶数看成 0
            // 可以看成是下面这个数组
            // [1,1,0,1,1]
            // 存储索引为 i 的这个元素是 nums[i]
            // nums[i] 要么是 0 ,要么是 1
            // & 表示按位于操作
            // a & b 两个位都为1时,结果才为1
            // nums[i] = 1,nums[i] & 1  = 1,也就是原数组位置为奇数时,nums[i] & 1 的结果是 1
            // nums[i] = 0,nums[i] & 1  = 0,也就是原数组位置为偶数时,nums[i] & 1 的结果是 0
            // pre 是统计奇数的和
            // 存储索引为 i 的这个元素时,前缀和的值是多少
            pre += nums[i] & 1;

            // 判断之前有没有存储 pre - k 这种前缀和
            if (mp.containsKey(pre - k)) {

                // 如果有,说明 pre - k 到 pre 直接的那些元素值之和就是 k
                // 找到了一组,累加到 count 上
                count += mp.get(pre - k);

            }

            // 这个值出现的频次存储到 mp 中
            // getOrDefault:当 Map 集合中有这个 key 时,就使用这个 key 对应的 value 值
            // 如果没有就使用默认值 defaultValue
            mp.put(pre, mp.getOrDefault(pre, 0) + 1);
        }

        // 返回结果
        return count;
    }
    
}

# **2、**C++ 代码

class Solution {
public:
    int numberOfSubarrays(vector<int>& nums, int k) {
        // 统计和为 K 的子数组的数量
        int count = 0;
        
        // 记录遍历到索引为 i 的这个元素时,前缀和的值是多少
        int pre = 0;

        // 利用哈希表,以前缀和为键,出现次数为对应的值,记录 pre[i] 出现的次数 
        unordered_map<int, int> mp;
        
        // 一开始,需要设置前缀和为 0 时,出现的次数为 1 次
        // 这一行的作用就是为了应对 nums[0] +nums[1] + ... + nums[i] == k 这种情况
        // 如数组 [1, 2, 3, 6]
        // 这个数组的累加和数组为 [1, 3, 【6】, 12] 
        // 如果 k = 6, 假如 mp 中没有预先存储(0, 1) 
        // 那么来到累加和为 6 的位置时,这时 mp 中存储的就只有两个数据 (1, 1), (3, 1)
        // 想去判断 mp.containsKey(pre - k) , 这时 pre - k = 6 - 6 = 0
        // 但 map 中没有 (0, 1) ,
        // 因为这个时候忽略了从下标 0 累加到下标 i 等于 k 的情况
        // 仅仅是统计了从下标大于 0 到某个位置等于 k 的所有答案
        mp[0] =  1;

        // 开始从头到尾遍历 nums 数组,在遍历过程中,会执行两个操作
        // 1、存储索引为 i 的这个元素时,前缀和的值是多少,并且把这个值出现的频次存储到 mp 中
        // 2、判断之前有没有存储 pre - k 这种前缀和,如果有,说明 pre - k 到 pre 直接的那些元素值之和就是 k
        for (int i = 0; i < nums.size(); i++) {

            // 存储索引为 i 的这个元素时,前缀和的值是多少
            pre += nums[i] & 1;

            // 判断之前有没有存储 pre - k 这种前缀和
            if (mp.find(pre - k) != mp.end()) {

                // 如果有,说明 pre - k 到 pre 直接的那些元素值之和就是 k
                // 找到了一组,累加到 count 上
                count += mp[pre - k];

            }

            // 这个值出现的频次存储到 mp 中
            mp[pre]++;
        }

        // 返回结果
        return count;
    }
};

# 3、Python 代码

class Solution:
    def numberOfSubarrays(self, nums: List[int], k: int) -> int:
        # 统计和为 K 的子数组的数量
        count = 0
        
        # 记录遍历到索引为 i 的这个元素时,前缀和的值是多少
        pre = 0

        # 利用哈希表,以前缀和为键,出现次数为对应的值,记录 pre[i] 出现的次数 
        mp = collections.defaultdict(int)

        # 一开始,需要设置前缀和为 0 时,出现的次数为 1 次
        # 这一行的作用就是为了应对 nums[0] +nums[1] + ... + nums[i] == k 这种情况
        # 如数组 [1, 2, 3, 6]
        # 这个数组的累加和数组为 [1, 3, 【6】, 12] 
        # 如果 k = 6, 假如 mp 中没有预先存储(0, 1) 
        # 那么来到累加和为 6 的位置时,这时 mp 中存储的就只有两个数据 (1, 1), (3, 1)
        # 想去判断 mp.containsKey(pre - k) , 这时 pre - k = 6 - 6 = 0
        # 但 map 中没有 (0, 1) ,
        # 因为这个时候忽略了从下标 0 累加到下标 i 等于 k 的情况
        # 仅仅是统计了从下标大于 0 到某个位置等于 k 的所有答案
        mp[0] =  1

        # 开始从头到尾遍历 nums 数组,在遍历过程中,会执行两个操作
        # 1、存储索引为 i 的这个元素时,前缀和的值是多少,并且把这个值出现的频次存储到 mp 中
        # 2、判断之前有没有存储 pre - k 这种前缀和,如果有,说明 pre - k 到 pre 直接的那些元素值之和就是 k
        for i in range(len(nums)) : 

            # 存储索引为 i 的这个元素时,前缀和的值是多少
            pre += nums[i] & 1

            # 判断之前有没有存储 pre - k 这种前缀和
            # 如果有,说明 pre - k 到 pre 直接的那些元素值之和就是 k
            # 找到了一组,累加到 count 上
            # 利用 defaultdict 的特性,当 presum - k 不存在时,返回的是 0
            count += mp[pre - k]
            
            # 这个值出现的频次存储到 mp 中
            # getOrDefault:当 Map 集合中有这个 key 时,就使用这个 key 对应的 value 值
            # 如果没有就使用默认值 defaultValue
            mp[pre] += 1

        # 返回结果
        return count